use async_graphql::SimpleObject;
use linera_sdk::linera_base_types::{Amount, ChainId};
use linera_sdk::views::{linera_views, MapView, RegisterView, RootView, ViewStorageContext};

use rfq::{RequestId, TokenPair, Tokens};

use crate::state::{ExchangeInProgress, QuoteRequested, RequestData, RequestState, TempChainState};

#[derive(RootView, SimpleObject)]
#[view(context = "ViewStorageContext")]
pub struct RfqState {
    next_seq_number: RegisterView<u64>,
    requests: MapView<RequestId, RequestData>,
    temp_chain_state: RegisterView<Option<TempChainState>>,
}

#[allow(dead_code)]
impl RfqState {
    pub fn create_new_request(
        &mut self,
        target: ChainId,
        token_pair: TokenPair,
        amount: Amount,
    ) -> u64 {
        let seq_number = *self.next_seq_number.get();
        let request_state = RequestState::QuoteRequested(QuoteRequested { token_pair, amount });
        self.requests
            .insert(
                &RequestId::new(target, seq_number, true),
                RequestData {
                    state: request_state,
                },
            )
            .expect("Couldn't insert a new request state");
        self.next_seq_number.set(seq_number + 1);
        seq_number
    }

    pub fn register_request(
        &mut self,
        request_id: RequestId,
        token_pair: TokenPair,
        amount: Amount,
    ) {
        self.requests
            .insert(
                &request_id.with_we_requested(false),
                RequestData {
                    state: RequestState::QuoteRequested(QuoteRequested { token_pair, amount }),
                },
            )
            .expect("Couldn't insert a new request state");
    }

    pub async fn cancel_request(&mut self, request_id: &RequestId) -> Option<ChainId> {
        let req_data = self
            .requests
            .get(request_id)
            .await
            .expect("ViewError")
            .expect("Request not found");
        match req_data.state {
            RequestState::ExchangeInProgress(ExchangeInProgress { temp_chain_id })
                if request_id.is_our_request() =>
            {
                Some(temp_chain_id)
            }
            _ => {
                self.requests.remove(request_id).expect("Request not found");
                None
            }
        }
    }

    pub async fn close_request(&mut self, request_id: &RequestId) {
        self.requests.remove(request_id).expect("Request not found");
    }

    pub async fn request_data(&mut self, request_id: &RequestId) -> Option<&mut RequestData> {
        self.requests.get_mut(request_id).await.expect("ViewError")
    }

    pub fn init_temp_chain_state(
        &mut self,
        request_id: RequestId,
        initiator: ChainId,
        token_pair: TokenPair,
        sent_tokens: Tokens,
    ) {
        self.temp_chain_state.set(Some(TempChainState {
            request_id,
            initiator,
            token_pair,
            tokens_in_hold: Some(sent_tokens),
        }));
    }

    pub fn is_temp_chain(&self) -> bool {
        self.temp_chain_state.get().is_some()
    }

    pub fn temp_chain_held_tokens(&self) -> Option<Tokens> {
        self.temp_chain_state
            .get()
            .as_ref()
            .and_then(|temp_state| temp_state.tokens_in_hold.clone())
    }

    pub fn take_temp_chain_held_tokens(&mut self) -> Option<Tokens> {
        self.temp_chain_state
            .get_mut()
            .as_mut()
            .and_then(|temp_state| temp_state.tokens_in_hold.take())
    }

    pub fn temp_chain_initiator_and_request_id(&self) -> (ChainId, RequestId) {
        let temp_chain_state = self
            .temp_chain_state
            .get()
            .as_ref()
            .expect("No TempChainState found!");
        (
            temp_chain_state.initiator,
            temp_chain_state.request_id.clone(),
        )
    }
}
